﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using Kawanon.Usability;
using Kawanon.QtHoney;
using QTPlugin;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
using QTPlugin.Interop;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Drawing.Design;
using System.IO;

namespace Kawanon.Usability.Actions
{
	public class Unknown : Action
	{
		public override string Name { get { return "不明"; } }

		public override void Execute(Arguments args)
		{
			throw new NotImplementedException();
		}
	}

	public class Pending : Action
	{
		public override string Name { get { return "未確定"; } }

		public override void Execute(Arguments args)
		{
			throw new NotImplementedException();
		}
	}

	[Serializable]
	public class MergeWindows : Action
	{
		public override string Name { get { return "ウィンドウをマージ"; } }

		public override void Execute(Arguments args)
		{
			//if (args.Server == null)
			//{
			//    DebugHelpers.Abort("Server == null");
			//}
			object tabBar = Utility.ReflectGetFieldValue(args.Server, "tabBar");
			if (tabBar == null)
			{
				Dbg.Abort("tabBar == null");
			}

			MethodInfo method = tabBar.GetType().GetMethod("MergeAllWindows",
				BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[0], null);
			if (method == null)
			{
				Dbg.Abort("method == null");
			}
			object ret = method.Invoke(tabBar, new Object[0]);

			//DebugHelpers.Echo(ret);
		}
	}

	[Serializable]
	public class ShowGestureSettings : Action
	{
		public override string Name { get { return "マウスジェスチャの設定"; } }

		public override void Execute(Arguments args)
		{
			Utility.ShowForm(args.Server, new GestureForm(args.Server));
		}
	}

	[Serializable]
	public class NextTab : Action
	{
		public override string Name { get { return "次のタブへ"; } }

		public override void Execute(Arguments args)
		{
			if (args.Server.SelectedTab == null)
			{
				return;
			}
			ITab[] tabs = args.Server.GetTabs();

			int index = args.Server.SelectedTab.Index;
			index++;
			if (index >= tabs.Length)
			{
				index = 0;
			}
			args.Server.SelectedTab = tabs[index];
		}
	}

	[Serializable]
	public class PreviousTab : Action
	{
		public override string Name { get { return "前のタブへ"; } }

		public override void Execute(Arguments args)
		{
			if (args.Server.SelectedTab == null)
			{
				return;
			}
			ITab[] tabs = args.Server.GetTabs();

			int index = args.Server.SelectedTab.Index;
			index--;
			if (index < 0)
			{
				index = tabs.Length - 1;
			}
			args.Server.SelectedTab = tabs[index];
		}
	}

	[Serializable]
	public class IsolateTab : Action
	{
		public override string Name { get { return "タブを新しいウィンドウに分離"; } }

		public override void Execute(Arguments args)
		{
			if (args.Server.SelectedTab == null)
			{
				return;
			}

			ITab tab = args.Server.SelectedTab;
			Address addr = tab.Address;
			tab.Close();

			args.Server.CreateWindow(addr);
		}
	}

	[Serializable]
	public class NewTab : Action
	{
		public override string Name { get { return "新しいタブで開く"; } }

		public override void Execute(Arguments args)
		{
			Address[] addrs = GetAddresses(args.Target);
			if (addrs == null)
			{
				return;
			}

			int index = 0;
			if (args.Server.SelectedTab != null)
			{
				index = args.Server.SelectedTab.Index + 1;
			}
			Array.ForEach(addrs, addr => args.Server.CreateTab(addr, index, false, true));
		}
	}

	[Serializable]
	public class NewWindow : Action
	{
		public override string Name { get { return "新しいウィンドウで開く"; } }

		public override void Execute(Arguments args)
		{
			Address[] addrs = GetAddresses(args.Target);
			if (addrs == null)
			{
				return;
			}

			Array.ForEach(addrs, addr => args.Server.CreateWindow(addr));
		}
	}

	[Serializable]
	class FileOperation : Action
	{
		private const uint SVGIO_BACKGROUND = 0;
		private const uint SVGIO_SELECTION = 0x1;
		private const uint SVGIO_ALLVIEW = 0x2;
		private const uint SVGIO_CHECKED = 0x3;
		private const uint SVGIO_TYPE_MASK = 0xf;
		private const uint SVGIO_FLAG_VIEWORDER = 0x80000000;

		private const uint CMF_NORMAL = 0x00000000;
		private const uint CMF_DEFAULTONLY = 0x00000001;
		private const uint CMF_VERBSONLY = 0x00000002;
		private const uint CMF_EXPLORE = 0x00000004;
		private const uint CMF_NOVERBS = 0x00000008;
		private const uint CMF_CANRENAME = 0x00000010;
		private const uint CMF_NODEFAULT = 0x00000020;
		private const uint CMF_INCLUDESTATIC = 0x00000040;
		private const uint CMF_EXTENDEDVERBS = 0x00000100;

		private const uint GCS_VERBW = 0x00000004;
		private const uint GCS_HELPTEXTW = 0x00000005;
		private const uint GCS_VALIDATEW = 0x00000006;
		private const uint GCS_UNICODE = 0x00000004;

		private const uint S_OK = 0;

		private const uint SW_SHOWNORMAL = 1;

		[DllImport("user32.dll")]
		static extern IntPtr CreatePopupMenu();

		[DllImport("user32.dll")]
		static extern bool DestroyMenu(IntPtr hMenu);

		[DllImport("user32.dll")]
		static extern uint GetMenuItemID(IntPtr hMenu, int nPos);

		[DllImport("user32.dll")]
		static extern int GetMenuItemCount(IntPtr hMenu);

		[StructLayout(LayoutKind.Sequential)]
		protected struct CMINVOKECOMMANDINFO
		{
			public int cbSize;    // sizeof(CMINVOKECOMMANDINFO)
			public int fMask;     // any combination of CMIC_MASK_*
			public IntPtr hwnd;      // might be NULL (indicating no owner window)
			public IntPtr lpVerb;    // either a string or MAKEINTRESOURCE(idOffset)
			public string lpParameters;  // might be NULL (indicating no parameter)
			public string lpDirectory;   // might be NULL (indicating no specific directory)
			public int nShow;     // one of SW_ values for ShowWindow() API
			public int dwHotKey;
			public IntPtr hIcon;
		}

		[ComImport]
		[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
		[Guid("000214f4-0000-0000-c000-000000000046")]
		protected interface IContextMenu2
		{
			[PreserveSig]
			int QueryContextMenu(IntPtr hMenu, uint indexMenu, int idCmdFirst, int idCmdLast, uint uFlags);

			[PreserveSig]
			int InvokeCommand(ref CMINVOKECOMMANDINFO pici);

			[PreserveSig]
			int GetCommandString(int idcmd, uint uflags, int reserved, StringBuilder commandstring, int cch);

			[PreserveSig]
			int HandleMenuMsg(uint uMsg, IntPtr wParam, IntPtr lParam);
		}

		private static Dictionary<string, string> s_names = new Dictionary<string, string>();

		static FileOperation()
		{
			s_names.Add("copy", "ファイルのコピー");
			s_names.Add("cut", "ファイルの切り取り");
			s_names.Add("paste", "ファイルのペースト");
		}

		private string m_verb;

		public override string Name { get { return s_names[m_verb]; } }

		public FileOperation(string verb)
		{
			m_verb = verb;
		}

		public override int GetHashCode()
		{
			return GetType().GetHashCode() ^ m_verb.GetHashCode();
		}

		protected bool InvokeCommandByVerb(IntPtr hwnd, IContextMenu2 contextMenu, string verb)
		{
			IntPtr hMenu = CreatePopupMenu();
			int maxId = contextMenu.QueryContextMenu(hMenu, 0, 1, 0x7fff, CMF_NOVERBS);

			bool ret = false;
			int count = GetMenuItemCount(hMenu);
			for (int i = 0; i < count; i++)
			{
				int id = (int)GetMenuItemID(hMenu, i);
				if (id == -1 || id == 0)
				{
					continue;
				}

				StringBuilder buf = new StringBuilder(256);
				contextMenu.GetCommandString(id - 1, GCS_VERBW, 0, buf, buf.Capacity);

				if (buf.ToString() == verb)
				{
					CMINVOKECOMMANDINFO ici = new CMINVOKECOMMANDINFO();
					ici.cbSize = Marshal.SizeOf(typeof(CMINVOKECOMMANDINFO));
					ici.hwnd = hwnd;
					ici.lpVerb = new IntPtr(id - 1);
					ici.nShow = (int)SW_SHOWNORMAL;
					ret = (contextMenu.InvokeCommand(ref ici) == S_OK);
					break;
				}
			}

			DestroyMenu(hMenu);
			return ret;
		}

		public override void Execute(Arguments args)
		{
			Address[] addrs = GetAddresses(args.Target);
			if (addrs == null)
			{
				return;
			}

			//IShellBrowser browser = (IShellBrowser)Utility.ReflectGetFieldValue(args.Server, "shellBrowser");
			//if (browser == null)
			//{
			//    Dbg.Abort("browser == null");
			//    return;
			//}

			//IShellView view;
			//if (browser.QueryActiveShellView(out view) != S_OK)
			//{
			//    Dbg.Abort("QueryActiveShellView Failed");
			//    return;
			//}

			if (args.ShellView == null)
			{
				Dbg.Abort("ShellView == null");
				return;
			}
			IntPtr hwnd;
			if (args.ShellView.GetWindow(out hwnd) != S_OK)
			{
				Dbg.Abort("GetWindow Failed");
				return;
			}

			Guid IID_IContextMenu = new Guid("000214e4-0000-0000-c000-000000000046");
			object pv;
			if (args.ShellView.GetItemObject(SVGIO_SELECTION, ref IID_IContextMenu, out pv) != S_OK)
			{
				Dbg.Play();
				return;
			}
			IContextMenu2 contextMenu = (IContextMenu2)pv;
			InvokeCommandByVerb(hwnd, contextMenu, m_verb);

		}
	}

	[Serializable]
	class SendKeys : CustomAction
	{
		private string m_name;
		private string m_keys;

		public override string Name { get { return m_name; } }

		public override bool IsAcceptTarget { get { return false; } }

		public override void Execute(Arguments args)
		{
			if (args.Server != null)
			{
				args.Server.ExecuteCommand(Commands.FocusFileList, null);
			}
			System.Windows.Forms.SendKeys.SendWait(m_keys);

			System.Windows.Forms.Application.DoEvents();
		}

		public SendKeys(string name, string keys)
		{
			m_name = name;
			m_keys = keys;
		}

		public override int GetHashCode()
		{
			return GetType().GetHashCode() ^ m_name.GetHashCode() ^ m_keys.GetHashCode();
		}

		public override System.Windows.Forms.DialogResult ShowEditDialog(IntPtr hwnd)
		{
			throw new NotImplementedException();
		}
	}

	[Serializable]
	public class ShowWindow : Action
	{
		const int SW_HIDE = 0;
		const int SW_SHOWNORMAL = 1;
		const int SW_NORMAL = 1;
		const int SW_SHOWMINIMIZED = 2;
		const int SW_SHOWMAXIMIZED = 3;
		public const int SW_MAXIMIZE = 3;
		const int SW_SHOWNOACTIVATE = 4;
		const int SW_SHOW = 5;
		public const int SW_MINIMIZE = 6;
		const int SW_SHOWMINNOACTIVE = 7;
		const int SW_SHOWNA = 8;
		public const int SW_RESTORE = 9;
		const int SW_SHOWDEFAULT = 10;
		const int SW_FORCEMINIMIZE = 11;

		[DllImport("user32.dll", EntryPoint="ShowWindow")]
		static extern bool _ShowWindow(IntPtr hWnd, int nCmdShow);

		private static Dictionary<int, string> s_names = new Dictionary<int, string>();

		static ShowWindow()
		{
			s_names.Add(SW_MAXIMIZE, "最大化");
			s_names.Add(SW_MINIMIZE, "最小化");
			s_names.Add(SW_RESTORE, "元のサイズに戻す");
		}

		private int m_cmdShow;

		public override string Name { get { return s_names[m_cmdShow]; } }

		public ShowWindow(int cmdShow)
		{
			m_cmdShow = cmdShow;
		}

		public override void Execute(Arguments args)
		{
			_ShowWindow(args.Server.ExplorerHandle, m_cmdShow);
		}

		public override int GetHashCode()
		{
			return GetType().GetHashCode() ^ m_cmdShow.GetHashCode();
		}
	}

	[Serializable]
	public class CreateProcess : CustomAction
	{
		[Serializable]
		struct Details	// コピー可能なように構造体で
		{
			private const string ExpandStringHelp = "QTTabBar の「アプリケーション」機能と同じキーワードを指定できます。 %f% = 選択したファイル, %d% = 選択したフォルダ, %s% = 選択したファイルとフォルダ, %c% = 現在開いているフォルダ, %cd% = 選択したフォルダまたは現在開いているフォルダ。 パスは括弧つきで連結されます。";
			// 概要:
			//     起動するアプリケーションまたはドキュメントを取得または設定します。
			//
			//TODO: ドキュメントの場合は UseShellExecute = true
			[Category("コマンドライン")]
			[DisplayName("ファイル名")]
			[Description(ExpandStringHelp)]
			[Editor(typeof(FileNameEditor), typeof(UITypeEditor))]
			public string FileName { get; set; }

			[Category("コマンドライン")]
			[DisplayName("引数")]
			[Description(ExpandStringHelp)]
			public string Arguments { get; set; }

			[Category("パラメータ")]
			[DisplayName("作業フォルダ")]
			[Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]
			[Description(ExpandStringHelp)]
			public string WorkingDirectory { get; set; }

			// 概要:
			//     プロセスを起動するときに使用するウィンドウの状態を取得または設定します。
			[Category("パラメータ")]
			[DisplayName("実行時の大きさ")]
			[Description("Normal = 通常表示, Hidden = 非表示, Minimized = 最小化, Maximized = 最大化")]
			public ProcessWindowStyle WindowStyle { get; set; }

			// YesNoTypeConverter を使う
			[Category("パラメータ")]
			[DisplayName("コンソールを表示する")]
			[TypeConverter(typeof(YesNoConverter))]
			public bool ShowConsole { get; set; }

			// 概要:
			//     プロセスを起動できなかったときにエラー ダイアログ ボックスを表示するかどうかを示す値を取得または設定します。
			[Category("パラメータ")]
			[DisplayName("エラーを表示する")]
			[TypeConverter(typeof(YesNoConverter))]
			public bool ErrorDialog { get; set; }

			//public object[] GetValues()
			//{
			//    return new object[] { FileName, Arguments, WorkingDirectory, WindowStyle, ShowConsole, ErrorDialog };
			//}
		}

		private Details m_details = new Details();

		public override string Name
		{
			get
			{
				return String.Format("{0} の実行", Path.GetFileNameWithoutExtension(m_details.FileName));
			}
		}

		// %1 などを渡せるように
		public override bool IsAcceptTarget { get { return true; } }

		public override void Execute(Arguments args)
		{
			Address[] addrs = GetAddresses(args.Target);

			Address currentFolder = new Address();
			if (args.Server != null && args.Server.SelectedTab != null)
			{
				currentFolder = args.Server.SelectedTab.Address;
			}
			PathExpander expander = new PathExpander(currentFolder, addrs);

			ProcessStartInfo info = new ProcessStartInfo();
			info.FileName = expander.Replace(m_details.FileName);
			info.Arguments = expander.Replace(m_details.Arguments);
			info.WorkingDirectory = expander.Replace(m_details.WorkingDirectory);
			info.WindowStyle = m_details.WindowStyle;
			info.CreateNoWindow = !m_details.ShowConsole;
			info.ErrorDialog = m_details.ErrorDialog;

			if (args.Server != null)
			{
				info.ErrorDialogParentHandle = args.Server.ExplorerHandle;
			}

			try
			{
				Process.Start(info);
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
		}

		public CreateProcess(string name, string keys) : this()
		{
		}

		public CreateProcess()
		{
		}

		public override System.Windows.Forms.DialogResult ShowEditDialog(IntPtr hwnd)
		{
			PropertiesForm form = new PropertiesForm();
			PropertyGrid grid = form.PropertyGrid;
			grid.SelectedObject = m_details;
			form.ShowDialog();

			Details details = (Details)grid.SelectedObject;
			if (details.FileName == null || details.FileName == String.Empty)
			{
				return DialogResult.Cancel;
			}

			m_details = details;
			return DialogResult.OK;
		}
	}

	[Serializable]
	class OpenApplication : CustomAction
	{

		public override string Name
		{
			get { return "{0} を開く"; }
		}

		public override bool IsAcceptTarget { get { return false; } }

		public override void Execute(Action.Arguments args)
		{
			//NOTE: 本体が未実装のため使えない...
			ProcessStartInfo[] apps = args.Server.GetApplications(null);
			Dbg.Echo(apps);

			foreach (ProcessStartInfo info in apps)
			{
				PropertiesForm form = new PropertiesForm();
				form.PropertyGrid.SelectedObject = info;
				form.ShowDialog();
			}
		}

		public override DialogResult ShowEditDialog(IntPtr hwnd)
		{
			return DialogResult.OK;
		}
	}

	[Serializable]
	class ShellBrowserAccelerator : Action
	{
		private const uint WM_KEYDOWN = 0x0100;

		private const uint S_OK = 0;

		public enum Accelerators
		{
			None,
			Cut,
			Copy,
			Paste,
			Undo,
		}

		struct Description
		{
			public string Name;
			public Keys Key;
			public Keys Modifier;

			public Description(string name, Keys key, Keys modifier)
			{
				Name = name;
				Key = key;
				Modifier = modifier;
			}
		}

		private static Dictionary<Accelerators, Description> s_accels = new Dictionary<Accelerators, Description>();

		static ShellBrowserAccelerator()
		{
			s_accels.Add(Accelerators.None, new Description("なし", Keys.None, Keys.None));
			s_accels.Add(Accelerators.Cut, new Description("切り取り", Keys.X, Keys.Control));
			s_accels.Add(Accelerators.Copy, new Description("コピー", Keys.C, Keys.Control));
			s_accels.Add(Accelerators.Paste, new Description("貼り付け", Keys.V, Keys.Control));
			s_accels.Add(Accelerators.Undo, new Description("元に戻す", Keys.Z, Keys.Control));
		}

		private Accelerators m_accel;

		public override string Name { get { return s_accels[m_accel].Name; } }

		public ShellBrowserAccelerator(Accelerators accel)
		{
			m_accel = accel;
		}

		public override void Execute(Arguments args)
		{
			IntPtr hwnd;

			if (args.ShellView.GetWindow(out hwnd) != S_OK)
			{
				Dbg.Abort("GetWindow Failed");
				return;
			}
			Description desc = s_accels[m_accel];
			MSG msg = new MSG();
			msg.hwnd = hwnd;
			msg.message = WM_KEYDOWN;
			msg.wParam = new IntPtr((int)desc.Key);
			msg.lParam = new IntPtr((int)desc.Modifier);

			//IShellBrowser browser = Utility.GetShellBrowser(args.Server);
			//if (browser == null)
			//{
			//    return;
			//}
			//int hr = browser.TranslateAcceleratorSB(ref msg, 0);
			//Dbg.Echo(hr);

			int hr = args.ShellView.TranslateAccelerator(ref msg);
			Dbg.Echo(hr);
		}
	}
}
